package mt.comm;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;

import mt.Order;

public class ServerCommImpl implements ServerComm {
	private static final int DEFAULT_SERVER_PORT = 6789;
	/**
	 * The number of threads for the ClientWorkers
	 */
	private static final int NUMBER_OF_DISPATCHER_THREADS = 3;
	private ServerSocket serverSocket;
	private BlockingQueue<ServerSideMessage> queue;
	private ConcurrentHashMap<String, Socket> clientMap = new ConcurrentHashMap<String, Socket>();
	private Thread serverListenerThread;

	@Override
	public void start() {
		// --- Starting the resources -----------------------------------------
		// ... the internal message queue
		// according to BlockingQueue javadoc:
		// "BlockingQueue implementations are thread-safe."
		queue = new LinkedBlockingQueue<ServerSideMessage>();
		// ... the server
		try {
			serverSocket = new ServerSocket(DEFAULT_SERVER_PORT);
		} catch (IOException e) {
		}
		// --- Listening and dispatching --------------------------------------
		serverListenerThread = new Thread(new ServerListener(clientMap, queue,
				serverSocket, NUMBER_OF_DISPATCHER_THREADS));
		serverListenerThread.start();
	}

	@Override
	public ServerSideMessage getNextMessage() {
		try {
			return queue.take();
		} catch (InterruptedException e) {
			return null;
		}
	}

	@Override
	public boolean hasNextMessage() {
		return queue.size() > 0;
	}

	@Override
	public void sendOrder(String receiversNickname, Order order) {
		Socket socket = clientMap.get(receiversNickname);
		if (socket != null) {
			ClientSideMessage message = new ClientSideMessageImpl(
					ClientSideMessage.Type.ORDER, order);
			try {
				sendToClient(message, socket);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	@Override
	public void sendError(String toNickname, String error) {
		Socket socket = clientMap.get(toNickname);
		if (socket != null) {
			ClientSideMessage message = new ClientSideMessageImpl(
					ClientSideMessage.Type.ERROR, error);
			try {
				sendToClient(message, socket);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	@Override
	public boolean clientIsConnected(String nickname) {
		return clientMap.containsKey(nickname);
	}

	@Override
	public void disconnectClient(String nickname) {
		try {
			Socket socket = clientMap.get(nickname);
			if (socket != null) {
				socket.close();
				clientMap.remove(nickname);
			}
		} catch (IOException e) {
		}
	}

	/**
	 * Stops the server. This method is not part of the {@link ServerComm}
	 * interface, but it is used in the tests.
	 */
	public void stop() {
		queue.clear();
		try {
			serverSocket.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Sends a {@link ClientSideMessage} to a client. This method requires and
	 * open and connected {@link Socket}.
	 * 
	 * @param clientSocket
	 *            A socket connected to the client to whom we want to send the
	 *            message.
	 * @param message
	 *            The message to be sent to the client.
	 * @throws IOException
	 *             If an I/O error occurs while writing to the streams.
	 */
	private void sendToClient(ClientSideMessage message, Socket clientSocket)
			throws IOException {
		// --- effectively sending the message --------------------------------
		ObjectOutputStream out = new ObjectOutputStream(
				clientSocket.getOutputStream());
		out.writeObject(message);
		out.flush();
	}
}